﻿using System;
using System.Collections.Generic;
using ASquared.ModelOptimization;
using ASquared.SymbolicMath;
using ASquared;
using Csu.Modsim.ModsimModel;
using Csu.Modsim.ModsimIO;

namespace iwpm.optimization
{
    /// <summary>An enumeration of all the possible objective formulations.</summary>
    public enum ObjectiveFormulation { UserDefined, HydroTargets, Lagrangian, EnvVsHydro, TwoBusSystem}

    /// <summary>A MODSIM _model that has optimizing features in it.</summary>
    public class OptiModel : IConditionalGradientSolvableModel
    {
        #region Instance variables

        private Model _model;
        private bool _succApprox = false;
        private string _filepath = "";
        private ObjectiveFormulation _objForm;
        private object _parameters;
        private IConditionalGradientSolvableFunction _obj;
        private Matrix _costs;
        private HydroDebugger _debugger = null;

        #endregion
        #region Properties

        /// <summary>Gets the MODSIM model for this instance.</summary>
        public Model MODSIMModel
        {
            get
            {
                return _model;
            }
        }
        /// <summary>Gets the file path associated with the _model for this instance.</summary>
        public string FilePath { get { return this._filepath; } }
        /// <summary>Gets the current iteration of the running _model.</summary>
        public int Iteration
        {
            get
            {
                return _model.mInfo.Iteration;
            }
        }
        /// <summary>Gets and sets the objective formulation for this instance.</summary>
        public ObjectiveFormulation ObjectiveForm
        {
            get
            {
                return this._objForm;
            }
            set
            {
                this._objForm = value;
            }
        }
        /// <summary>Gets and sets the parameters for the particular objective formulation used for optimization.</summary>
        /// <remarks>This can only be set after the objective formulation is set.</remarks>
        public object Parameters
        {
            get
            {
                return _parameters;
            }
            set
            {
                _parameters = value;
                switch (this._objForm)
                {
                    case ObjectiveFormulation.UserDefined:
                        break;
                    case ObjectiveFormulation.HydroTargets:
                        this._obj = new HydroTargetSeeker(_model, (double)_parameters);
                        break;
                    case ObjectiveFormulation.Lagrangian:
                        this._obj = new LagrangianHydro(_model, (LagrangianHydroInputs)_parameters);
                        break;
                    case ObjectiveFormulation.EnvVsHydro:
                        this._obj = new EnvVsHydro(_model, (EnvVsHydroInputs)_parameters);
                        break; 
                    case ObjectiveFormulation.TwoBusSystem:
                        this._obj = new TwoBusSystem(_model, (TwoBusSystemInputs)_parameters);
                        break; 
                    default:
                        throw new NotImplementedException("The optimization ObjectiveFormulation." + _objForm.ToString() + " is not implemented in OptiModel.");
                }
            }
        }
        /// <summary>Gets and sets the objective function for this instance. When setting this variable, the ObjectiveForm property is automatically set to UserDefined regardless of the user-defined class type.</summary>
        public IConditionalGradientSolvableFunction objective
        {
            get { return this._obj; }
            set { this._obj = value; this._objForm = ObjectiveFormulation.UserDefined; }
        }

        #endregion

        #region Constructors

        /// <summary>Constructor</summary>
        /// <param name="_model">A MODSIM _model that defines the _model components of this </param>
        public OptiModel(string filePath, ObjectiveFormulation formulation, object objectiveParameters)
        {
            _model = new Model();
            XYFileReader.Read(_model, filePath);
            this.Setup(_model, formulation, objectiveParameters);
        }
        /// <summary>Constructor</summary>
        /// <param name="_model">A MODSIM _model that defines the _model components of this </param>
        public OptiModel(Model model, ObjectiveFormulation formulation, object objectiveParameters)
        {
            this.Setup(model, formulation, objectiveParameters);
        }
        /// <summary>Sets up an instance of OptiModel.</summary>
        /// <param name="_model">The MODSIM _model.</param>
        /// <param name="formulation">The formulation of the objective function to select.</param>
        /// <param name="objectiveParameters">The parameters for teh objective function.</param>
        public void Setup(Model model, ObjectiveFormulation formulation, object objectiveParameters)
        {
            _model = model;
            _model.Compliant_Init += new EventHandler(OnInit);
            _model.Compliant_Converged += new EventHandler(OnIterBottom);
            _model.Compliant_ConvergedFinal += new EventHandler(OnConverged);
            _filepath = _model.fname;
            _objForm = formulation;
            this.Parameters = objectiveParameters;
            _succApprox = (_model.hydro.IterativeTechnique == IterativeSolutionTechnique.SuccApprox);
        }

        #endregion

        #region Methods

        /// <summary>Runs the _model.</summary>
        public void Run()
        {
            // ensure that the hydropower controller is active for objective functions requiring it.
            if (this.ObjectiveForm == ObjectiveFormulation.HydroTargets
                || this.ObjectiveForm == ObjectiveFormulation.Lagrangian)
                _model.hydro.IsActive = true;

            if (this.objective == null)
                throw new NullReferenceException("The objective function in the IConditionalGradientSolvableModel must not be null.");
            Csu.Modsim.ModsimModel.Modsim.RunSolver(_model);
        }
        /// <summary>Resets the MODSIM model costs.</summary>
        public void Reset()
        {
            if (_costs != null)
                this.objective.costs = _costs;
            if (_succApprox)
                _model.hydro.IterativeTechnique = IterativeSolutionTechnique.SuccApprox;
        }
        /// <summary>Tells this instance to help in debugging the model by placing output in the specified file.</summary>
        /// <param name="outputfile">The file in which debug values are stored.</param>
        public void Debug(string outputfile)
        {
            if (_obj is IHydroDebuggable)
            {
                _debugger = new HydroDebugger(outputfile, (IHydroDebuggable)_obj);
                if (_debugger.FailedToOpen)
                    Console.WriteLine("Failed to open hydropower debug file... Is it in use?");
            }
            else
            {
                Console.WriteLine("Object {0} is not IHydroDebuggable.", new object[] { _obj });
            }
        }

        #endregion
        #region Events

        public event EventHandler Init;
        public event EventHandler IterBottom;
        public event EventHandler Converged;
        void OnInit(object sender, EventArgs e)
        {
            // pass event through
            EventHandler eHand = this.Init;
            if (eHand != null) eHand(sender, e);

            // Store initial information
            _costs = this.objective.costs;
        }
        void OnIterBottom(object sender, EventArgs e)
        {
            _model.hydro.IterativeTechnique = IterativeSolutionTechnique.Nothing;
            EventHandler eHand = this.IterBottom;
            if (eHand != null) eHand(sender, e);
        }
        void OnConverged(object sender, EventArgs e)
        {
            EventHandler eHand = this.Converged;
            if (eHand != null) eHand(sender, e);
        }

        #endregion
    }
}
